﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using TiaoTiaoCode.Common.Extensions;

namespace TiaoTiaoCode.Common.Helpers
{
    /// <summary>
    /// CPK计算类型
    /// </summary>
    public enum CpkType
    {
        /// <summary>
        /// 规格上限+规格中心
        /// </summary>
        [Description("规格上限+规格中心")]
        Cpu,

        /// <summary>
        /// 规格下限
        /// </summary>
        [Description("规格下限+规格中心")]
        Cpl,

        /// <summary>
        /// 双边规格
        /// </summary>
        [Description("双边规格")]
        Cp
    }

    /// <summary>
    /// CPK 计算类
    /// 名词定义：
    ///     CPK 制程能力指数
    ///     Ca 制程准确度
    ///     Cp 制程精密度
    ///     USL 规格上限
    ///     LSL 规格下限
    ///     C 规格中心
    ///     X 平均值
    ///     T 规格公差
    ///     δ（sigma）为数据的标准差
    /// 公式：
    ///     Cpk = Cp(1-|Ca|)  ==> Cp 可能是Cpu Cpl Cp
    ///     Ca = (X-C) / (T/2)
    ///     规格上限和规格中心的规格
    ///         Cpu = (USL-X) / 3δ
    ///     规格下限和规格中心的规格
    ///         Cpl = (X-LSL) / 3δ
    ///     双边规格无规格中心的规格
    ///         Cp = (USL-LSL) / 6δ
    /// </summary>
    public class TiaoTiaoCPK
    {
        /// <summary>
        /// 计算CPK
        /// </summary>
        /// <param name="values"></param>
        /// <param name="usl"></param>
        /// <param name="lsl"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static decimal Cpk(List<decimal> values, decimal usl, decimal lsl, CpkType type = CpkType.Cp)
        {
            // 均值
            var x = values.Average();

            // 规格中心
            var c = (usl + lsl) / 2;

            // 规格公差
            var t = usl - lsl;

            // 准确度
            var ca = t == 0 ? 0 : (x - c) / (t / 2);

            // 标准差
            var sigma = values.TiaoTiaoStDev();

            // 制程精密度
            var cp = Cp(type, usl, lsl, x, sigma);

            return cp * (1 - Math.Abs(ca));
        }

        /// <summary>
        /// 根据类型计算CPK值
        /// </summary>
        /// <param name="type"></param>
        /// <param name="usl"></param>
        /// <param name="lsl"></param>
        /// <param name="avg"></param>
        /// <param name="sigma"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        static decimal Cp(CpkType type, decimal usl, decimal lsl, decimal avg, decimal sigma)
        {
            return type switch
            {
                CpkType.Cpu => (usl - avg) / (3 * sigma),
                CpkType.Cpl => (avg - lsl) / (3 * sigma),
                CpkType.Cp => (usl - lsl) / (6 * sigma),
                _ => throw new ArgumentNullException("CpkType")
            };
        }
    }
}
